//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_CONTEXT_EGL_H
#define _UXS_CONTEXT_EGL_H

#pragma once

#ifdef PLATFORM_TYPE_MOBILE

#include "../../../stl_core/ux_stl_core.h"
#include "egl.h"

namespace UX_STL
{
namespace _platform_
{

	//
	// Context EGL
	//

	struct ContextEGL
	{
	// types
	public:
		typedef bool (*InitWindowProc_t) (void *window, int format, void *parameter);
		typedef bool (*ReleaseWindowProc_t) (void *window, void *parameter);
		typedef void (*OnContextLostProc_t) (ContextEGL *context, void *parameter);
		

		struct Descriptor
		{
			enum EColorFormat
			{
				R8_G8_B8_A8		= 0x08080808,
				R4_G4_B4_A4		= 0x04040404,
				R5_G6_B5		= 0x05060500,

				RGBA8			= R8_G8_B8_A8,
				RGBA4			= R4_G4_B4_A4,
				RGB565			= R5_G6_B5,
			};

			enum EDepthFromat
			{
				NO_DEPTH		= 0,
				DEPTH_16		= 16,
				DEPTH_24		= 24,
				DEPTH_32		= 32,
			};

			enum EStencilFormat
			{
				NO_STENCIL		= 0,
				STENCIL_8		= 8,
			};

			enum EVersion
			{
				OPENGLES_1_0	= 0x10,
				OPENGLES_1_1	= 0x11,
				OPENGLES_2_0	= 0x20,
				OPENGLES_3_0	= 0x30,
			};

			EColorFormat	colorFormat;	// R, G, B, A size in bits
			uint8			depthBits;
			uint8			stencilBits;
			uint8			multiSamples;
			uint8			version;
			///
			Descriptor (EColorFormat colorFmt = RGBA8,
						uint8		 depth    = DEPTH_16,
						uint8		 stencil  = NO_STENCIL,
						uint8		 samples  = 0,
						uint8		 ver	  = OPENGLES_2_0 ) :
				colorFormat(colorFmt), depthBits(depth),
				stencilBits(stencil), multiSamples(samples),
				version(ver) {}
			
			void SetFormat (int r, int g, int b, int a, int d, int s, int ms)
			{
				depthBits		= d;
				stencilBits		= s;
				colorFormat		= EColorFormat( (a & 0xFF) | ((b & 0xFF) << 8) | ((g & 0xFF) << 16) | ((r & 0xFF) << 24) );
				multiSamples	= ms;
			}

			int RedBits ()		const	{ return (colorFormat >> 24) & 0xFF; }
			int GreenBits ()	const	{ return (colorFormat >> 16) & 0xFF; }
			int BlueBits ()		const	{ return (colorFormat >>  8) & 0xFF; }
			int AlphaBits ()	const	{ return (colorFormat & 0xFF); }

			int MajorVersion()	const	{ return version >> 4; }
			int MinorVersion()	const	{ return version & 0xF; }
		};


	// variables
	private:
        EGLConfig				_config;
        EGLDisplay				_display;
        EGLSurface				_surface;
        EGLContext				_context;
        EGLNativeWindowType		_window;
		InitWindowProc_t		_initWindow;
		ReleaseWindowProc_t		_releaseWindow;
		OnContextLostProc_t		_onContextLost;
		void *					_parameter;
		int						_version;


	// methods
	private:
		bool _CheckError (uni_c_string eglcall, uni_c_string func, uni_c_string file, int line) const;
		
		static bool _DummyInitWindow (void *window, int format, void *parameter)	{ return true; }
		static bool _DummyReleaseWindow (void *window, void *parameter)				{ return true; }
		static void _DummyOnContextLost (ContextEGL *context, void *parameter)		{}
		
#	ifdef PLATFORM_ANDROID
		static bool _DefaultInitWindow (void *window, int format, void *parameter);
		static bool _DefaultReleaseWindow (void *window, void *parameter);
#	endif


	public:
		ContextEGL ();
		~ContextEGL ();

		void SetDefaultCallbacks ();
		void SetCallbacks (	InitWindowProc_t init,
							ReleaseWindowProc_t release,
							OnContextLostProc_t lostContext,
							void * parameter);

		bool Initialize (Descriptor &desc);
		void Destroy ();

		bool CreateSurface (void *window);
		void DestroySurface ();
		bool IsSurfaceCreated () const;

		bool SwapBuffers ();
		bool SwapInterval (int interval = 1);

		bool MakeCurrent ();
		bool ResetContext ();
		bool IsContextCurrent () const;
		bool IsContextInitialized () const;

		uvec2 SurfaceSize () const;
		fvec2  SurfaceResolution () const;

		int  Version () const	{ return _version; }
	};


#	define EGL_CALL( _func )				{ _func;  _CheckError( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ); }
#	define EGL_CALL_B( _func, _res )		{ _func;  _res &= _CheckError( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ); }
#	define EGL_CALL_R( _func, _ret )		{ _func;  if ( !_CheckError( #_func, UX_FUNCTION_NAME, __FILE__, __LINE__ ) ) return _ret; }


	
	// constructor
	inline ContextEGL::ContextEGL () :
		_config(null), _display(EGL_NO_DISPLAY), _surface(EGL_NO_SURFACE), _context(EGL_NO_CONTEXT),
		_window(null), _initWindow(null), _releaseWindow(null), _onContextLost(null),
		_version(0), _parameter(null)
	{
		SetCallbacks( null, null, null, null );
	}

	// destructor
	inline ContextEGL::~ContextEGL ()
	{
	}
	
	// SetDefaultCallbacks
	inline void ContextEGL::SetDefaultCallbacks ()
	{
		return SetCallbacks( &_DefaultInitWindow, &_DefaultReleaseWindow, null, this );
	}

	// SetWindowProcs
	inline void ContextEGL::SetCallbacks (InitWindowProc_t init,
										  ReleaseWindowProc_t release,
										  OnContextLostProc_t lostContext,
										  void * parameter)
	{
		_initWindow		= init == null			? &_DummyInitWindow		: init;
		_releaseWindow	= release == null		? &_DummyReleaseWindow	: release;
		_onContextLost	= lostContext == null	? &_DummyOnContextLost	: lostContext;
		_parameter		= parameter;
	}
	
	// Initialize
	inline bool ContextEGL::Initialize (Descriptor &desc)
	{
		CHECK_ERR( desc.version >= Descriptor::OPENGLES_2_0 );

		EGL_CALL( _display = eglGetDisplay( EGL_DEFAULT_DISPLAY ) );
		CHECK_ERR( _display != EGL_NO_DISPLAY );

		EGLint maj_ver, min_ver;
		EGL_CALL( eglInitialize( _display, &maj_ver, &min_ver ) == EGL_TRUE );
		_version = maj_ver * 100 + min_ver * 10;

		EGL_CALL( eglBindAPI( EGL_OPENGL_ES_API ) );

		static const EGLint config_attribs[] =
		{
			EGL_SURFACE_TYPE,		EGL_WINDOW_BIT,
			EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
			EGL_RED_SIZE,			4,
			EGL_GREEN_SIZE,			4,
			EGL_BLUE_SIZE,			4,
			EGL_NONE
		};

		int num_configs = 0;
            
		EGL_CALL( eglChooseConfig( _display, config_attribs, null, 0, &num_configs ) );
		CHECK_ERR( num_configs > 0 );


		array< EGLConfig >	configs( num_configs );
		
		EGL_CALL( eglChooseConfig( _display, config_attribs, configs.ptr(), configs.Count(), &num_configs ) );

		struct Util
		{
			static EGLint FindAttrib (EGLDisplay display, EGLConfig config, EGLint attrib, EGLint defValue = 0)
			{
				EGLint result = 0;
				return eglGetConfigAttrib( display, config, attrib, &result ) == EGL_TRUE ? result : defValue;
			}
		};
		
		// TODO: find near config
		for (int i = 0; i < num_configs; ++i)
		{
			int d = Util::FindAttrib( _display, configs[i], EGL_DEPTH_SIZE );
			int s = Util::FindAttrib( _display, configs[i], EGL_STENCIL_SIZE );

			if ( d < desc.depthBits or
				 s < desc.stencilBits )
			{
				continue;
			}

			int r = Util::FindAttrib( _display, configs[i], EGL_RED_SIZE );
			int g = Util::FindAttrib( _display, configs[i], EGL_GREEN_SIZE );
			int b = Util::FindAttrib( _display, configs[i], EGL_BLUE_SIZE );
			int a = Util::FindAttrib( _display, configs[i], EGL_ALPHA_SIZE );

			//int ms = Util::FindAttrib( _display, configs[i], EGL_SAMPLES );

			if ( r == desc.RedBits()   and
				 g == desc.GreenBits() and
				 b == desc.BlueBits()  and
				 a == desc.AlphaBits() /*and
				 ms == desc.multiSamples*/ )
			{
				_config = configs[i];
				break;
			}
		}

		if ( _config == null )
		{
			_config = configs[0];
		}

		{
			int d  = Util::FindAttrib( _display, _config, EGL_DEPTH_SIZE   );
			int s  = Util::FindAttrib( _display, _config, EGL_STENCIL_SIZE );
			int r  = Util::FindAttrib( _display, _config, EGL_RED_SIZE     );
			int g  = Util::FindAttrib( _display, _config, EGL_GREEN_SIZE   );
			int b  = Util::FindAttrib( _display, _config, EGL_BLUE_SIZE    );
			int a  = Util::FindAttrib( _display, _config, EGL_ALPHA_SIZE   );
			int ms = 0; //Util::FindAttrib( _display, _config, EGL_SAMPLES      );

			desc.SetFormat( r, g, b, a, d, s, ms );
		}

		const EGLint context_attribs[] = {
			EGL_CONTEXT_CLIENT_VERSION, desc.MajorVersion(),
			EGL_NONE
		};
		
		EGL_CALL( _context = eglCreateContext( _display, _config, EGL_NO_CONTEXT, context_attribs ) );
		CHECK_ERR( _context != EGL_NO_CONTEXT );
		
		return true;
	}

	// Destroy
	inline void ContextEGL::Destroy ()
	{
		DestroySurface();
		
		if ( _context != EGL_NO_CONTEXT )
		{
			EGL_CALL( eglDestroyContext( _display, _context ) == EGL_TRUE );
			_context = EGL_NO_CONTEXT;
		}

		if ( _display != EGL_NO_DISPLAY )
		{
			EGL_CALL( eglTerminate( _display ) == EGL_TRUE );
			_display = EGL_NO_DISPLAY;
		}
	}

	// CreateSurface
	inline bool ContextEGL::CreateSurface (void *window)
	{
		CHECK_ERR( window != null );
		CHECK_ERR( _context != EGL_NO_CONTEXT and _display != EGL_NO_DISPLAY );

		DestroySurface();

		_window = (EGLNativeWindowType) window;

		EGLint format = 0;
		EGL_CALL( eglGetConfigAttrib( _display, _config, EGL_NATIVE_VISUAL_ID, &format ) );

		CHECK_ERR( _initWindow( _window, format, _parameter ) );

		const EGLint surface_attribs[] = {
			EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
			EGL_NONE
		};
		EGL_CALL( _surface = eglCreateWindowSurface( _display, _config, _window, surface_attribs ) );
		CHECK_ERR( _surface != EGL_NO_SURFACE );
            
		EGL_CALL_R( eglMakeCurrent( _display, _surface, _surface, _context ) == EGL_TRUE, false );
		return true;
	}

	// DestroySurface
	inline void ContextEGL::DestroySurface ()
	{
		if ( _surface != EGL_NO_SURFACE )
		{
			EGL_CALL( eglMakeCurrent( _display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ) );
			EGL_CALL( eglDestroySurface( _display, _surface ) );
			_surface = EGL_NO_SURFACE;
		}

		if ( _window != null )
		{
			CHECK_ERR( _releaseWindow( _window, _parameter ), );
			_window = null;
		}
	}
	
	// IsSurfaceCreated
	inline bool ContextEGL::IsSurfaceCreated () const
	{
		return _surface != EGL_NO_SURFACE;
	}
	
	// MakeCurrent
	inline bool ContextEGL::MakeCurrent ()
	{
		EGL_CALL_R( eglMakeCurrent( _display, _surface, _surface, _context ) == EGL_TRUE, false );
		return true;
	}

	// ResetContext
	inline bool ContextEGL::ResetContext ()
	{
		EGL_CALL_R( eglMakeCurrent( _display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ) == EGL_TRUE, false );
		return true;
	}
	
	// IsContextCurrent
	inline bool ContextEGL::IsContextCurrent () const
	{
		EGLContext context = eglGetCurrentContext();
		return context == _context;
	}
	
	//IsContextInitialized
	inline bool ContextEGL::IsContextInitialized () const
	{
		return _context != EGL_NO_CONTEXT;
	}

	// SwapBuffers
	inline bool ContextEGL::SwapBuffers ()
	{
		//EGL_CALL( eglWaitNative( EGL_CORE_NATIVE_ENGINE ) );
		//EGL_CALL( eglWaitGL() );
		EGL_CALL_R( eglSwapBuffers( _display, _surface ) == EGL_TRUE, false );
		return true;
	}
	
	// SwapInterval
	inline bool ContextEGL::SwapInterval (int interval)
	{
		EGL_CALL_R( eglSwapInterval( _display, interval ), false );
		return true;
	}

	// SurfaceSize
	inline uvec2 ContextEGL::SurfaceSize () const
	{
		uvec2 result;
		EGL_CALL( eglQuerySurface( _display, _surface, EGL_WIDTH, (EGLint *)&result.x ) );
		EGL_CALL( eglQuerySurface( _display, _surface, EGL_HEIGHT, (EGLint *)&result.y ) );
		return result;
	}

	// SurfaceResolution
	inline fvec2 ContextEGL::SurfaceResolution () const
	{
		EGLint	w = 0,
				h = 0;
		EGL_CALL( eglQuerySurface( _display, _surface, EGL_HORIZONTAL_RESOLUTION, &w ) );
		EGL_CALL( eglQuerySurface( _display, _surface, EGL_VERTICAL_RESOLUTION, &h ) );
		return fvec2( w, h ) / (float)EGL_DISPLAY_SCALING;
	}

	// _CheckError
	inline bool ContextEGL::_CheckError (uni_c_string eglcall, uni_c_string func, uni_c_string file, int line) const
	{
		EGLint err = eglGetError();
		
		if ( err == EGL_SUCCESS )
			return true;

		string	str("EGL error: ");

		switch ( err )
		{
			case EGL_NOT_INITIALIZED :		str << "EGL_NOT_INITIALIZED";		break;
			case EGL_BAD_ACCESS :			str << "EGL_BAD_ACCESS";			break;
			case EGL_BAD_ALLOC :			str << "EGL_BAD_ALLOC";				break;
			case EGL_BAD_ATTRIBUTE :		str << "EGL_BAD_ATTRIBUTE";			break;
			case EGL_BAD_CONFIG :			str << "EGL_BAD_CONFIG";			break;
			case EGL_BAD_CONTEXT :			str << "EGL_BAD_CONTEXT";			break;
			case EGL_BAD_CURRENT_SURFACE :	str << "EGL_BAD_CURRENT_SURFACE";	break;
			case EGL_BAD_DISPLAY :			str << "EGL_BAD_DISPLAY";			break;
			case EGL_BAD_MATCH :			str << "EGL_BAD_MATCH";				break;
			case EGL_BAD_NATIVE_PIXMAP :	str << "EGL_BAD_NATIVE_PIXMAP";		break;
			case EGL_BAD_NATIVE_WINDOW :	str << "EGL_BAD_NATIVE_WINDOW";		break;
			case EGL_BAD_PARAMETER :		str << "EGL_BAD_PARAMETER";			break;
			case EGL_BAD_SURFACE :			str << "EGL_BAD_SURFACE";			break;

			case EGL_CONTEXT_LOST :
				_onContextLost( (ContextEGL *)this, _parameter );
				str << "EGL_CONTEXT_LOST";
				break;

			default :
				break;
		}

		str << ", in " << eglcall << ", function " << func << ", file: \"" << file
			<< "\", line: " << string().FormatI( line, 10 );

		LOG( str.cstr(), e_log::ERROR );

		return false;
	}
	

#	ifdef PLATFORM_ANDROID
	
	// _DefaultInitWindow
	inline bool ContextEGL::_DefaultInitWindow (void *window, int format, void *parameter)
	{
		using namespace android;
		ANativeWindow_setBuffersGeometry( (ANativeWindow *)window, 0, 0, format );
		return true;
	}

	// _DefaultReleaseWindow
	inline bool ContextEGL::_DefaultReleaseWindow (void *window, void *parameter)
	{
		using namespace android;
		ANativeWindow_release( (ANativeWindow *)window );
		return true;
	}

#	endif


}	// _platform_
}	// UX_STL

#endif	// PLATFORM_TYPE_MOBILE
#endif	// _UXS_CONTEXT_EGL_H